package utils

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
)

// 封装反射修改数据
func Set(o interface{}, field string, newValue interface{}) error {
	v := reflect.ValueOf(o)
	if v.Kind() != reflect.Ptr {
		return fmt.Errorf("请传入指针类型")
	}
	v = v.Elem()
	if !v.CanSet() {
		return fmt.Errorf("当前类型不可修改")
	}
	f := v.FieldByName(field)
	if !f.IsValid() {
		return fmt.Errorf("%s 不可修改", field)
	}
	newValueType := reflect.TypeOf(newValue)
	if !newValueType.AssignableTo(f.Type()) {
		return fmt.Errorf("%#v 类型不能赋值给 %#v", newValueType.Kind().String(), f.Type().String())
	}
	f.Set(reflect.ValueOf(newValue))
	return nil
}

func SetStruct(o interface{}, data map[string]interface{}) error {
	t := reflect.TypeOf(o)
	if t.Kind() != reflect.Ptr {
		return errors.New("o 必须为指针")
	}
	if t.Elem().Kind() != reflect.Struct {
		return errors.New("o 必须为结构体")
	}
	for k, v := range data {
		field, ok := t.Elem().FieldByName(k)
		if !ok {
			continue
		}
		if field.Type == reflect.TypeOf(v) {
			val := reflect.ValueOf(v)
			reflect.ValueOf(o).Elem().FieldByName(k).Set(val)
		}
	}
	return nil
}

// 反射方法调用
func Cell(o interface{}, methodName string, params ...interface{}) ([]reflect.Value, error) {
	v := reflect.ValueOf(o)
	if v.Kind() != reflect.Ptr {
		return nil, fmt.Errorf("o 必须是指针类型")
	}
	method := v.MethodByName(methodName)

	if len(params) != method.Type().NumIn() {
		return nil, errors.New("the number of input params not match!")
	}
	in := make([]reflect.Value, len(params))
	for index, v := range params {
		// 参数类型判断
		param := method.Type().In(index)
		//if reflect.ValueOf(v).Kind() != param.Kind() {
		if param.ConvertibleTo(reflect.ValueOf(v).Type()) {
			return nil, errors.New("参数类型转换失败")
		}
		in[index] = reflect.ValueOf(v)
	}
	return method.Call(in), nil
}

// 通过 tag:default 标签设置默认值
func FieldDefault(o interface{}) error {
	rt := reflect.TypeOf(o)
	rv := reflect.ValueOf(o)

	if rv.Kind() != reflect.Ptr {
		return fmt.Errorf("o (non-pointer %v)", rv.Kind())
	}
	tElem := rt.Elem()
	vElem := rv.Elem()
	numField := tElem.NumField()
	for i := 0; i < numField; i++ {
		var (
			fieldStruct = tElem.Field(i)
			field       = vElem.Field(i)
			tagVar      = fieldStruct.Tag.Get("default")
			ft          = field.Type() // fieldStruct.Type
		)

		if tagVar != "" {
			switch ft.Kind() {
			case reflect.Bool:
				x, err := strconv.ParseBool(tagVar)
				if err != nil {
					return fmt.Errorf("bad default bool %q: %v", tagVar, err)
				}
				field.SetBool(x)
			case reflect.Float32:
				x, err := strconv.ParseFloat(tagVar, 32)
				if err != nil {
					return fmt.Errorf("bad default float32 %q: %v", tagVar, err)
				}
				field.Set(reflect.ValueOf(float32(x)))
			case reflect.Float64:
				x, err := strconv.ParseFloat(tagVar, 64)
				if err != nil {
					return fmt.Errorf("bad default float64 %q: %v", tagVar, err)
				}
				field.Set(reflect.ValueOf(x))
			case reflect.Int32:
				x, err := strconv.ParseInt(tagVar, 10, 32)
				if err != nil {
					return fmt.Errorf("bad default int32 %q: %v", tagVar, err)
				}
				field.Set(reflect.ValueOf(int32(x)))
			case reflect.Int64:
				x, err := strconv.ParseInt(tagVar, 10, 64)
				if err != nil {
					return fmt.Errorf("bad default int64 %q: %v", tagVar, err)
				}
				field.Set(reflect.ValueOf(x))
			case reflect.String:
				field.Set(reflect.ValueOf(tagVar))
			case reflect.Uint8:
				x, err := strconv.ParseUint(tagVar, 10, 8)
				if err != nil {
					return fmt.Errorf("bad default uint8 %q: %v", tagVar, err)
				}
				field.SetUint(x)
			case reflect.Uint32:
				x, err := strconv.ParseUint(tagVar, 10, 32)
				if err != nil {
					return fmt.Errorf("bad default uint32 %q: %v", tagVar, err)
				}
				field.Set(reflect.ValueOf(uint32(x)))
			case reflect.Uint64:
				x, err := strconv.ParseUint(tagVar, 10, 64)
				if err != nil {
					return fmt.Errorf("bad default uint64 %q: %v", tagVar, err)
				}
				field.Set(reflect.ValueOf(x))
			case reflect.Int:
				x, err := strconv.Atoi(tagVar)
				if err != nil {
					return fmt.Errorf("bad default int %q: %v", tagVar, err)
				}
				field.Set(reflect.ValueOf(x))
			default:
				return fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind())
			}

		}
	}
	return nil
}
